package igloo;

import igloo.aws.IglooDynamoClient;
import igloo.aws.IglooGlacierClient;
import igloo.aws.Region;
import igloo.controller.IglooController;
import igloo.model.Igloo;
import igloo.model.Table;
import igloo.ui.BusyPanel;
import igloo.ui.GradientPanel;
import igloo.ui.IglooPanel;
import igloo.ui.LoginPanel;

import java.awt.Color;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.UIManager;

import net.miginfocom.swing.MigLayout;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodb.model.CreateTableRequest;
import com.amazonaws.services.dynamodb.model.DescribeTableRequest;
import com.amazonaws.services.dynamodb.model.DescribeTableResult;
import com.amazonaws.services.dynamodb.model.KeySchema;
import com.amazonaws.services.dynamodb.model.KeySchemaElement;
import com.amazonaws.services.dynamodb.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodb.model.ScalarAttributeType;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

public class Application
{
    private static final Application INSTANCE = new Application();

    public static Application getInstance()
    {
        return INSTANCE;
    }

    private final ExecutorService mExecutor = Executors.newCachedThreadPool();

    private AWSCredentials mCredentials;

    private IglooDynamoClient mDynamoClient;
    private Map<Region, IglooGlacierClient> mGlacierClients = new HashMap<Region, IglooGlacierClient>();

    private Igloo mIgloo = new Igloo();
    private IglooController mIglooController;

    private JFrame mFrame;
    private GradientPanel mGradientPanel;
    private LoginPanel mLoginPanel;
    private BusyPanel mBusyPanel;
    private IglooPanel mIglooPanel;

    private Application()
    {
    }

    public ExecutorService getExecutor()
    {
        return mExecutor;
    }

    public IglooDynamoClient getDynamoClient()
    {
        return mDynamoClient;
    }

    public IglooGlacierClient getGlacierClient(Region region)
    {
        if(mCredentials == null)
            return null;

        IglooGlacierClient client = mGlacierClients.get(region);
        if(client == null)
        {
            client = new IglooGlacierClient(mCredentials, region, mExecutor);
            mGlacierClients.put(region, client);
        }

        return client;
    }

    public JFrame getFrame()
    {
        return mFrame;
    }

    public void start()
    {
        mFrame = new JFrame("Igloo - Amazon Glacier Client");
        mFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        mFrame.setIconImage(new ImageIcon("images/ice.png").getImage());
        mFrame.setSize(1024, 768);

        mGradientPanel = new GradientPanel();
        mGradientPanel.setEndColor(new Color(79, 129, 189));
        mGradientPanel.setStartColor(new Color(220, 230, 242));
        mGradientPanel.setLayout(new MigLayout("center, filly"));

        mLoginPanel = new LoginPanel();
        mLoginPanel.setCallback(new LoginPanel.Callback() {
            @Override
            public void login(String accessKey, char[] secretKey)
            {
                Application.this.login(accessKey, secretKey);
            }
        });

        mGradientPanel.add(mLoginPanel);

        mFrame.setContentPane(mGradientPanel);
        mFrame.setVisible(true);
    }

    private void login(String accessKey, char[] secretKey)
    {
        mGradientPanel.remove(mLoginPanel);

        mBusyPanel = new BusyPanel();
        mBusyPanel.setText("Authenticating...");

        mGradientPanel.add(mBusyPanel);
        mGradientPanel.validate();
        mGradientPanel.repaint();

        mCredentials = new BasicAWSCredentials(accessKey, new String(secretKey));

        mDynamoClient = new IglooDynamoClient(mCredentials, mExecutor);

        mBusyPanel.setText("Verifying tables...");

        List<String> tableNames = new ArrayList<String>(2);
        tableNames.add(Table.VAULT);
        tableNames.add(Table.ARCHIVE);
        tableNames.add(Table.JOB);
        ListenableFuture<Map<String, Boolean>> future = mDynamoClient.hasTables(tableNames);
        Futures.addCallback(future, new FutureCallback<Map<String, Boolean>>() {
            @Override
            public void onSuccess(Map<String, Boolean> result)
            {
                for(String tableName : result.keySet())
                {
                    if(!result.get(tableName))
                    {
                        EventQueue.invokeLater(new Runnable() {
                            @Override
                            public void run()
                            {
                                mBusyPanel.setText("Creating tables...");
                            }
                        });

                        if(tableName.equalsIgnoreCase(Table.VAULT))
                            createVaultTable();
                        else if(tableName.equalsIgnoreCase(Table.ARCHIVE))
                            createArchiveTable();
                        else
                            createJobTable();
                    }
                }

                for(String tableName : result.keySet())
                {
                    if(!result.get(tableName))
                    {
                        awaitTableState(mDynamoClient, tableName, "ACTIVE", 5000);
                    }
                }

                mIglooController = new IglooController(mIgloo);

                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run()
                    {
                        mIglooPanel = new IglooPanel(mIglooController);
                        mFrame.setContentPane(mIglooPanel);
                        mFrame.validate();

                        mIglooController.sync();
                    }
                });
            }

            @Override
            public void onFailure(Throwable t)
            {
                t.printStackTrace();
                System.exit(0);
            }
        });
    }

    public void createTables()
    {
        createVaultTable();
        createArchiveTable();
    }

    private void createVaultTable()
    {
        KeySchemaElement hashKeyElement = new KeySchemaElement();
        hashKeyElement.setAttributeType(ScalarAttributeType.S);
        hashKeyElement.setAttributeName("region");

        KeySchemaElement rangeKeyElement = new KeySchemaElement();
        rangeKeyElement.setAttributeType(ScalarAttributeType.S);
        rangeKeyElement.setAttributeName("name");

        KeySchema schema = new KeySchema();
        schema.setHashKeyElement(hashKeyElement);
        schema.setRangeKeyElement(rangeKeyElement);

        ProvisionedThroughput throughput = new ProvisionedThroughput();
        throughput.setReadCapacityUnits(3L);
        throughput.setWriteCapacityUnits(1L);
        CreateTableRequest request = new CreateTableRequest(Table.VAULT, schema);
        request.setProvisionedThroughput(throughput);

        mDynamoClient.getClient().createTable(request);
    }

    private void createArchiveTable()
    {
        KeySchemaElement hashKeyElement = new KeySchemaElement();
        hashKeyElement.setAttributeType(ScalarAttributeType.S);
        hashKeyElement.setAttributeName("vaultId");

        KeySchemaElement rangeKeyElement = new KeySchemaElement();
        rangeKeyElement.setAttributeType(ScalarAttributeType.S);
        rangeKeyElement.setAttributeName("id");

        KeySchema schema = new KeySchema();
        schema.setHashKeyElement(hashKeyElement);
        schema.setRangeKeyElement(rangeKeyElement);

        ProvisionedThroughput throughput = new ProvisionedThroughput();
        throughput.setReadCapacityUnits(5L);
        throughput.setWriteCapacityUnits(2L);

        CreateTableRequest request = new CreateTableRequest(Table.ARCHIVE, schema);
        request.setProvisionedThroughput(throughput);

        mDynamoClient.getClient().createTable(request);
    }

    private void createJobTable()
    {
        KeySchemaElement hashKeyElement = new KeySchemaElement();
        hashKeyElement.setAttributeType(ScalarAttributeType.S);
        hashKeyElement.setAttributeName("vaultId");

        KeySchemaElement rangeKeyElement = new KeySchemaElement();
        rangeKeyElement.setAttributeType(ScalarAttributeType.S);
        rangeKeyElement.setAttributeName("id");

        KeySchema schema = new KeySchema();
        schema.setHashKeyElement(hashKeyElement);
        schema.setRangeKeyElement(rangeKeyElement);

        ProvisionedThroughput throughput = new ProvisionedThroughput();
        throughput.setReadCapacityUnits(2L);
        throughput.setWriteCapacityUnits(2L);

        CreateTableRequest request = new CreateTableRequest(Table.JOB, schema);
        request.setProvisionedThroughput(throughput);

        mDynamoClient.getClient().createTable(request);
    }

    private static String getTableStatus(IglooDynamoClient client, String table)
    {
        DescribeTableRequest request = new DescribeTableRequest();
        request.setTableName(table);

        DescribeTableResult result = client.getClient().describeTable(request);
        return result.getTable().getTableStatus();
    }

    private static void awaitTableState(IglooDynamoClient client, String table, String state,
            int interval)
    {
        String status = getTableStatus(client, table);
        while(!status.equalsIgnoreCase(state))
        {
            status = getTableStatus(client, Table.VAULT);

            try
            {
                Thread.sleep(interval);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        Application.getInstance().start();
    }
}
